package rbb.itm5.bwinf.roboter.Control;

import java.util.ArrayList;

import rbb.itm5.bwinf.roboter.Model.Obstacle;

/**
 *
 * @author Dominik Jakielski
 *
 * Diese Klasse stellt alle nötigen Daten bzw. Funktionen für das
 * Steuerungsprogramm bereit. Alle Steruerungsprogramme müssen von dieser Klasse
 * erben,
 *
 */
public abstract class DriverInterface implements Runnable {

    /**Statuskonstante für laufenden Zustand*/
    public static final int StateRunning = 1;
    /**Statuskonstante für pausierenden Zustand*/
    public static final int StatePaused = 2;
    /**Statuskonstante für gestoppten Zustand*/
    public static final int StateStoped = 3;
    /**Statuskonstante für fertigen Zustand*/
    public static final int StateReady = 4;
    /**
     * Roboter-Objekt das den grafischen Roboter steuert
     */
    protected Roboter robot;
    private Thread t;
    private boolean breaked = false, finished = false;
    /**
     * In dieser Liste können alle erkannten Hindernisse erfasst werden.
     */
    protected ArrayList<Obstacle> obstacles = new ArrayList<Obstacle>();
    /**
     * Schalter ob die erkannten Hindernisse visualisiert werden sollen
     */
    public boolean paintObstacles = false;
    /**
     * Schalter ob die gegangen Schritte auf der Karte gezeigt werden sollen
     */
    public boolean paintGoneSteps = false;
    /**
     * Wenn Schalter aktiv ist, wird die Berechnungsdauer von der Wartezeit
     * abgezogen
     */
    public boolean realWaitIntervall = true;
    private int waitTime = 100;
    private long lastWaitTimeStamp = 0, startTime = 0;

    /**
     * Initalisiert das Fahrer-Programm.
     *
     * @param robot Controler-Objekt der den Roboter steuert.
     */
    public DriverInterface(Roboter robot) {
        this.robot = robot;
    }

    /**
     * Startet das Steuerungsprogramm und stellt alle Werte auf die Anfangswerte
     * zurück.
     */
    void start() {
        if (t == null || !t.isAlive()) {
            t = new Thread(this);
            t.start();
        }
    }

    /**
     * Pausiert das Steuerungsprogramm. Um das Programm fortsetzen zu können,
     * muss die start() Methode aufgerufen werden.
     */
    void pause() {
        if (t != null && t.isAlive()) {
            t.interrupt();
            t = null;
            breaked = true;
        }

    }

    /**
     * Stoppt das Steuerungsprogramm. Nach dem Stopp kann nur ein start()
     * erfolgen.
     */
    void stop() {
        stop(false);
    }

    /**
     * Stoppt das Steuerungsprogramm. Nach dem Stopp kann nur ein start()
     * erfolgen.
     *
     * @param goal Wurde das Ziel erreicht? Wenn ja wird das Programm ebenfalls
     * gestoppt.
     */
    void stop(boolean goal) {
        finished = finished || goal;
        if (t != null && t.isAlive()) {
            t.interrupt();
            t = null;
        }
    }

    /**
     *
     */
    @Override
    /**
     * Thread der die Steuerung übernimmt
     */
    public void run() {
        if (breaked) {
            processStateEvent(StateRunning);
            breaked = false;
        } else {
            robot.reset();
            obstacles.clear();
            processStateEvent(StateRunning);
        }
        try {
            robot.getEvent().refreshModel();
            startTime = System.currentTimeMillis();
            drive();
            if (t != null && !t.isInterrupted()) {
                processStateEvent(StateReady);
            }
        } catch (InterruptedException e) {
            if (finished) {
                processStateEvent(StateReady);
            } else if (breaked) {
                processStateEvent(StatePaused);
            } else {
                processStateEvent(StateStoped);
            }
        }
    }

    /**
     * Wartet die waitTime ab, und setzt anschließend das Programm fort
     *
     * @throws InterruptedException Die Exeption wird aufgerufen wenn das
     * Steuerungsprogramm(Driver) angehalten bzw. Pausiert werden soll.
     *
     */
    protected boolean waitForNextStep() throws InterruptedException {
        long wait = waitTime;
        if (lastWaitTimeStamp != 0 && realWaitIntervall) {
            wait = waitTime - (System.currentTimeMillis() - lastWaitTimeStamp) - 1;
            if (wait < 1) {
                wait = 1;
            }
        }
        Thread.sleep(wait);
        lastWaitTimeStamp = System.currentTimeMillis();
        return true;
    }

    /**
     * Setzt die Intervall Zeit fest, in der ein einzelner Schritt durchgeführt
     * wird.
     *
     * @param waitTime Zeit in ms bis der nächste Schritt erfolgt.
     */
    void setWaitTime(int waitTime) {
        if (waitTime > 0 && waitTime < 5000) {
            this.waitTime = waitTime;
            //WaitTime sofort aktivieren
            pause();
            start();
        }
    }

    /**
     * Diese Methode enthält die vollständige Steuerungslogik. Alle wichtigen
     * Schritte finden hier statt.
     *
     * @throws InterruptedException Die Exeption wird aufgerufen wenn das
     * Steuerungsprogramm(Driver) angehalten bzw. Pausiert werden soll.
     */
    protected abstract void drive() throws InterruptedException;

    /**
     * Gibt den Namen der Programmierers zurück, der den Fahrer programmiert hat
     */
    public abstract String getName();

    /**
     * Ist das Programm Pausiert?
     *
     * @return Ist das Programm Pausiert?
     */
    public boolean isPaused() {
        return (t == null || !t.isAlive()) || breaked;
    }

    /**
     * Ist das Programm Gestoppt?
     *
     * @return Ist das Programm Gestoppt?
     */
    public boolean isStoped() {
        return (t == null || !t.isAlive()) && !breaked;
    }

    public boolean isPaintObstacles() {
        return paintObstacles;
    }

    public boolean isPaintGoneSteps() {
        return paintGoneSteps;
    }

    private void processStateEvent(int State) {
        switch (State) {
            case StateRunning:
                if (breaked) {
                    onResume();
                } else {
                    onStart();
                }
                break;
            case StateStoped:
                onStop();
                break;
            case StatePaused:
                onPause();
                break;
            case StateReady:
                onReady();
                break;
            default:
                throw new IllegalArgumentException("Unkow State");
        }
        robot.getEvent().driverStateChanged(State);
    }

    /**
     * Ein Eventhandler der nach dem Start des Programms aufgerufen wird
     */
    protected void onStart() {
    }

    /**
     * Ein Eventhandler der nach dem Pausieren des Programms aufgerufen wird
     */
    protected void onPause() {
    }

    /**
     * Ein Eventhandler der nach dem die Pausierung des Programms beendet wird
     * und die Steuerung wieder läuft
     */
    protected void onResume() {
    }

    /**
     * Ein Eventhandler der nach dem beenden des Programms aufgerufen wird
     */
    protected void onStop() {
    }

    /**
     * Ein Eventhandler der aufgerufen wird, nachdem der Roboter sein Ziel
     * gefunden hat
     */
    protected void onReady() {
    }

    @Override
    public String toString() {
        return getName();
    }

    /**
     * Gibt die aktuelle Intervallzeit zurück.
     * @return  Gibt die aktuelle Intervallzeit zurück.
     */
    public int getWaitTime() {
        return waitTime;
    }

    /**
     * Aktuelle Laufzeitdauer in MS seit dem Start.
     * @return Gibt die Laufzeitdauer zurück
     */
    public long getRuntime() {
        return System.currentTimeMillis() - startTime;
    }
}
